package com.useful.secure.aspect;

import com.useful.core.api.ResultCode;
import com.useful.core.exception.BizException;
import com.useful.core.utils.ClassUtil;
import com.useful.core.utils.Func;
import com.useful.secure.annotation.PreAuth;
import com.useful.secure.auth.AuthFun;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.MethodParameter;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.stream.Stream;

/**
 * @author : wenchao.long
 * @date : Created in 2021/9/23 20:09 周四
 * @description :
 */
@Aspect
@Component
public class AuthAspect implements ApplicationContextAware {

    private static final ExpressionParser PARSER = new SpelExpressionParser();

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Around("@annotation(com.useful.secure.annotation.PreAuth) ||" +
            "@within(com.useful.secure.annotation.PreAuth)")
    public Object preAuth(ProceedingJoinPoint point) throws Throwable {
        if (handleAuth(point)) {
            return point.proceed();
        }
        throw new BizException(ResultCode.UN_AUTHORIZED);
    }

    private boolean handleAuth(ProceedingJoinPoint point) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        // 读取权限注解 优先方法上 没有则读取类
        PreAuth preAuth = ClassUtil.getAnnotation(method, PreAuth.class);
        String condition = preAuth.value();
        if (Func.isNotBlank(condition)) {
            Expression expression = PARSER.parseExpression(condition);
            // 方法参数值
            Object[] args = point.getArgs();
            StandardEvaluationContext context = getEvaluationContext(method, args);
            return Boolean.TRUE.equals(expression.getValue(context, Boolean.class));
        }
        return false;
    }

    private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) {
        // 初始化 spel表达式上下文 并设置AuthFun 做鉴权设置
        StandardEvaluationContext context = new StandardEvaluationContext(new AuthFun());
        // 设置表达式支持spring bean
        context.setBeanResolver(new BeanFactoryResolver(applicationContext));
        Stream.iterate(0, item -> item + 1)
                .limit(args.length)
                .forEach(i -> {
                    // 读取方法参数
                    MethodParameter parameter = ClassUtil.getMethodParameter(method, i);
                    // 设置方法 参数名和值 为 spel变量
                    context.setVariable(parameter.getParameterName(), args[i]);
                });
        return context;
    }
}
